/*
 * sys.c - Syscalls implementation
 */

#include <devices.h>
#include <io.h>
#include <errno.h>
#include <utils.h>
#include <sched.h>
#include <mm.h>
#include <mm_address.h>
#include <list.h>
#include <string.h>
#include <stats.h>
#include <sys.h>
#include <string.h>
#include <acces_modes.h>
#include <zeosfat.h>

#define BUFFER_SIZE 256

int comprova_fd(int fd,int mode) {
	if (!(current()->channels[fd])) return -EBADF;
	if (!(current()->channels[fd]->acces_mode&mode)) return -EINVAL;
	return 0;
}


/*NOT IMPLEMENTED SYSCALL*/
int sys_ni_syscall()
{
	return -ENOSYS;
}


/*SYSCALL #1*/
void sys_exit() 
{
  int i;
  union task_union * t;

  /*Comprovem que el proces init (pid==0) no faci la crida*/
  if (current()->Pid == 0) return;

  /*Alliberem les pagines de dades*/
  for (i = 0; i < NUM_PAG_DATA;++i)
    free_frame(current()->data_frames[i]);
  /*Alliberem els smafors*/
  for (i = 0; i < NR_SEM;++i){
    if ((current()->Pid == sems[i].owner) && (sems[i].init == INITIALIZED)) sys_sem_destroy(i);
  }
  /*Afegim el task struct a la cua de lliures*/
  list_del(&(current()->p_list));
  list_add(&(current()->p_list), &freequeue);

  /*Cambiem el context al seguent proces de la cua de processos actius*/
  t= (union task_union *)list_head_to_task_struct(runqueue.next);
  t->task.remaining_life = t->task.quantum;
  task_switch(t);
}

/*SYSCALL #2*/
int sys_fork() 
{
  int i, fr;
  union task_union * ts;

  /*Si no queden pagines suficients pel nou proces, retornem error*/
  if ((frames_free-NUM_PAG_DATA) < 0) return -EAGAIN;

  /*Obtenim un task struct lliure pel nou proces 
    Si no queden retornem error*/
  ts = get_free_task();
  if (ts == NULL) return -ENOMEM;

  /*Copiem les pagines de dades del proces pare al proces fill*/
  copy_data((void *) current(), (void *) &(ts->task),PAGE_SIZE);
  for (i = 0; i < NUM_PAG_DATA; ++i)
  {
    /*Reservem la pagina k de memoria, si no s'ha pogut adquirir,fem un rollback: 
      alliberar el task struct i les pagines ja reservades.
      Si l'hem adquirida, la guardem, i copiem del pare la pagina k-esima.
      Per fer-ho utilitcem una entrada extra a la taula de pagines del pare (la 21)*/ 
    fr=alloc_frame();   
    if (fr < 0) 
    {
      for (fr = 0; fr < i; ++fr)
      {
        free_frame(ts->task.data_frames[fr]);
      }
      list_add(&(ts->task.p_list), &freequeue);
      return -EAGAIN;
    }
    ts->task.data_frames[i] = fr;
    set_ss_pag(PAG_LOG_INIT_DATA_P0+NUM_PAG_DATA+i, fr);
    copy_data((void *) ((PAG_LOG_INIT_DATA_P0+i)<<12), (void *) ((PAG_LOG_INIT_DATA_P0+NUM_PAG_DATA+i)<<12), PAGE_SIZE);
    del_ss_pag(PAG_LOG_INIT_DATA_P0+NUM_PAG_DATA+i);
  } 

  /*invalidem el TLB */
  set_cr3();

  /*Inicialitcem els valors del nou proces*/
  ++next_pid;
  ts->task.Pid = next_pid;
  ts->task.cs = 0;
  ts->task.tics = 0;
  ts->task.quantum = current()->quantum;
  ts->task.remaining_life = current()->remaining_life;
  ts->task.read_size = 0;
  
  /*Copiem la taula de canals i incrementem les referencies als dispositius*/
  for (i=0; i<NR_CHANNELS; i++)
  {
    ts->task.channels[i] = current()->channels[i];
    if (ts->task.channels[i] != NULL) 
      ts->task.channels[i]->nref++;
  }

  /*Retornem el valor del registre %eax a la pila de kernel, per tal que el 
    valor de retorn del fill al tornar del fork sigui 0 ("soc el proces fill")*/
  ts->stack[KERNEL_STACK_SIZE-10]=0;

  /*Finalment encuem el proces a la cua de procesos actius*/
  list_add(&(ts->task.p_list), &runqueue);

  return next_pid;
}


/*SYSCALL #3*/
int sys_read(int fd,char *buffer, int size)
{
  //comprovem els parametres
  int error = comprova_fd(fd,O_RDONLY);
	if (error < 0) return error;
  if (!buffer || !access_ok(WRITE,buffer,size)) return -EFAULT;
  if (size < 0) return -EINVAL;
  
  //retornem el nombre de bytes llegits
  return current()->channels[fd]->file->ops.sys_read_dep(fd,buffer,size);
}


/*SYSCALL #4*/
int sys_write(int fd,char *buffer, int size)
{
	int tam = 0;
	//creem un buffer al espai de memoria del kernel
	char buf_sys[BUFFER_SIZE];
	int error = comprova_fd(fd,O_WRONLY);
	//comprovem els parametres
	if (error < 0) return error;
	if (!buffer || !access_ok(WRITE,buffer,size)) return -EFAULT;
	if (size < 0) return -EINVAL;
	//Comencem a copiar i a escriure al buffer de kernel
	while (size > BUFFER_SIZE)
	{
		copy_from_user(buffer+tam,buf_sys,BUFFER_SIZE);
		tam+=current()->channels[fd]->file->ops.sys_write_dep(fd,buf_sys,BUFFER_SIZE);
		size-=BUFFER_SIZE;
	}
	if (size > 0) 
	{
		copy_from_user(buffer+tam,buf_sys,size);
		tam+=current()->channels[fd]->file->ops.sys_write_dep(fd,buf_sys,size);
	}
	//retornem el nombre de bytes escrits
	return tam;
}


/* SYSCALL #5 */
int sys_open(const char *path,int flags)
{
    struct logic_device *file;
    struct TFA_item *new_opened_file;
    int ch;

    if(!access_ok(READ, path, FILE_NAME_SIZE)) return -EFAULT;
    if(strlen(path)>10) return -ENAMETOOLONG;
    if(!(flags & 0x03) || (flags & 0xF0)) return -EINVAL;

    ch = get_channel();
    if(ch == -1) return -EMFILE;

    new_opened_file = get_TFA_free_entry();
    if(!new_opened_file) return -ENFILE;
    

    file = search_logic_device(path);
    if(!file) {
        if(flags < O_CREAT) return -ENOENT;
        
        if(first_free_block == EOF) return -ENOSPC;

        file = new_file(path,flags&0x3);
        if(!file) return -ENOSPC;
    }
        else if((flags & 0x0C) == 0x0C) return -EEXIST;

    flags &= 0x03;

        if (flags != file->acces_mode && file->acces_mode != O_RDWR)
        return -EACCES;

        new_opened_file->nref++;
        new_opened_file->seq_pos = 0;
        new_opened_file->acces_mode = flags;
        new_opened_file->file = file;

        current()->channels[ch] = new_opened_file;
        return ch;
}

/*SYSCALL #6*/
int sys_close(int fd)
{
  if (fd < 0 || fd > NR_CHANNELS || current()->channels[fd] == NULL) return -EBADF;
  current()->channels[fd]->nref--;
  if (current()->channels[fd]->nref==0) current()->channels[fd]->file= NULL;
  current()->channels[fd] = NULL;
  return 1;
}

/*SYSCALL #10*/
int sys_unlink(const char * path)
{
  int i;
  struct logic_device *file;
  if(!access_ok(READ, path, FILE_NAME_SIZE)) return -EFAULT;
  if(strlen(path)>10) return -ENAMETOOLONG;
  for (i = 0; i < TFA_SIZE;++i)
  {
    if (TFA[i].nref > 0 && strcmp(TFA[i].file->name,path)==0) return -EBUSY;
  }
  file = search_logic_device(path);
  if(file == NULL) return -ENOENT;
  if(file->ops.sys_unlink_dep != NULL) return file->ops.sys_unlink_dep(file);
  else return -EPERM;
  return 0;
}

/*SYSCALL #20*/
int sys_getpid()
{
  return current()->Pid;
}


/*SYSCALL #21*/
int sys_sem_init(int n_sem, unsigned int value) 
{
  /*Comprovem si el numero del semafor esta dins el rang valid i si esta inicialitzat*/
  if (n_sem < 0 || n_sem >= NR_SEM )    return -EINVAL;
  if (sems[n_sem].init==INITIALIZED)    return -EBUSY;

  /*Asignem el semafor al proces que l'ha demanat amb el contador indicat.
    Inicialitcem la cua del semafor */
  sems[n_sem].owner = current()->Pid; 
  sems[n_sem].count = value;
  INIT_LIST_HEAD(&sems[n_sem].queue);
  sems[n_sem].init = INITIALIZED;

  return 0;
}


/*SYSCALL #22*/
int sys_sem_wait(int n_sem) 
{
  /*Comprovem si el numero del semafor esta dins el rang valid i si esta inicialitzat
    Si el proces que fa el wait es el proces init (pid==0) retornem error*/
  if (n_sem < 0 || n_sem >= NR_SEM || sems[n_sem].init==UNINITIALIZED) return -EINVAL;
  if (current()->Pid==0) return -EPERM;
    
  /*Si el contador es major a 0 decrementem el comptador*/
  if (sems[n_sem].count > 0)
  {
    sems[n_sem].count--;
  }
  /*bloquejem el proces y agafem el seguent de la cua de run*/
  else
  {    
    block_task(&(sems[n_sem].queue));
  } 
  return 0;
}


/*SYSCALL #23*/
int sys_sem_signal(int n_sem) 
{
  struct list_head * lt;
  union task_union * t;

  /*Comprovem si el numero del semafor esta dins el rang valid i si esta inicialitzat*/
  if (n_sem < 0 || n_sem >= NR_SEM || sems[n_sem].init==UNINITIALIZED) return -EINVAL;
       
  /*Si no hi ha cap proces bloquejat incrementemel comptador*/
  if (list_empty(&(sems[n_sem].queue)))
  {
    sems[n_sem].count++;
  }
  /*Si hi ha procesos bloquejats, desbloquejem el primer de la cua i l'encuem 
    a la cua de processos actius*/
  else if (!list_empty(&(sems[n_sem].queue)))
  {
    lt = list_first(&(sems[n_sem].queue));
    list_del(lt);
    list_add_tail(lt, &runqueue);  
    t = (union task_union *)list_head_to_task_struct(lt);

    /*Modifiquem el registre %eax de la pila de kernel del proces desbloquejat
      per tal que el valor de retorn al tornar del sem_wait sigui 0 (execucio correcta)*/
    t->stack[KERNEL_STACK_SIZE-10]=0;
  }
  return 0;
}


/*SYSCALL #24*/
int sys_sem_destroy(int n_sem) 
{
  struct list_head * pos;
  union task_union * t;
  
  /*Comprovem si el numero del semafor esta dins el rang valid i si esta inicialitzat
    Si el proces que fa la crida no te asignat el semafor n_sem retornem error*/
  if (n_sem < 0 || n_sem >= NR_SEM || sems[n_sem].init==UNINITIALIZED) return -EINVAL;  
  if (sems[n_sem].owner != current()->Pid) return -EPERM;
    
  /*Alliverem tots els processos bloquejats al semafor n_sem*/
  pos = sems[n_sem].queue.next;  
  while (pos != &(sems[n_sem].queue))
  {   
      t = (union task_union *)list_head_to_task_struct(pos);
      pos = pos->next;  
      list_del(&(t->task.p_list));
      list_add_tail(&(t->task.p_list), &runqueue);

      /*Modifiquem el registre %eax de la pila de kernel del proces desbloquejat
        per tal que el valor de retorna al tornar del sem_wait sigui -1 (execucio incorrecta)*/
      t->stack[KERNEL_STACK_SIZE-10]=-1;             
  }
  /*Alliverem el semafor*/
  sems[n_sem].init = UNINITIALIZED;

  return 0;
}


/*SYSCALL #34*/
int sys_nice(int quantum) 
{
  int old;

  /*Comprobem que quantum sigui un valor valid*/
  if (quantum <= 0) return -EINVAL;

  /*Asignem el nou quantum i retornem l'antic*/
  old = current()->quantum;
  current()->quantum = quantum;
  return old;
}


/*SYSCALL #35*/
int sys_get_stats(int pid, struct stats *st) 
{
  struct task_struct * tsk;
  struct stats stt;

  /*Comprovem que el pid sigui valid*/
  if(pid < 0) return -EINVAL;
  /*Comprovem si podem escriure a la memoria apuntada per st*/
  if(!access_ok(WRITE, st, sizeof(struct stats)))
      return -EFAULT;

  /*Busquem el task struct del proces amb el pid demanat.
    Si no existeix retornem error*/
  tsk=get_task_struct_by_pid(pid);
  if(tsk == NULL) return -ESRCH;

  /*Copiem les dades al struct local*/
  stt.tics=tsk->tics;
  stt.cs=tsk->cs;
  stt.remaining_quantum=tsk->remaining_life;

  /*Les copiem a l'espai d'usuari*/
  copy_to_user((void *) &stt, (void *)st, sizeof(struct stats));

  return 0;
}

/*SYSCALL #41*/
int sys_dup(int fd)
{
  int newfd;  
  /* comprobem si fd esta dins el rang de canals valids, si apunta a un canal obert, 
     i si queden canals lliures*/
  if (fd < 0 || fd > NR_CHANNELS || current()->channels[fd] == NULL) return -EBADF;
  if ((newfd = get_channel())<0) return -EMFILE;
  
  /* copiem el canal i increment les referencies a l'entrada de la TFA*/
  current()->channels[newfd] = current()->channels[fd];
  current()->channels[newfd]->nref++;
  
  return newfd;
}

